package cn.mesmile.lock.controller;

import cn.hutool.core.util.StrUtil;
import cn.mesmile.lock.aop.LockAction;
import cn.mesmile.lock.aop.LogAction;
import cn.mesmile.lock.entity.Book;
import lombok.AllArgsConstructor;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author zb
 * @date 2020/11/30 11:45
 * @Description
 */
@AllArgsConstructor
@RequestMapping("/api/v1/book")
@RestController
public class BookController {

    private final StringRedisTemplate stringRedisTemplate;

    private final RedisTemplate<String, Object> redisTemplate;

    private final RedissonClient redissonClient;

    @GetMapping("/notLock")
    public String notLock(HttpServletRequest request){
        int localPort = request.getLocalPort();
        System.out.println(">>>>>>>>>>>>>>  我的端口号是："+ localPort);
        String stock = stringRedisTemplate.opsForValue().get("stock");
        int i = Integer.parseInt(stock);
        if (i > 0) {
            Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
            System.out.println(">>>>>>>>>>>>>>> 扣除成功，剩余库存："+ lastNumber);
        } else {
            System.out.println(">>>>>>>>>>>>>>> 扣除失败库存失败");
        }
        return "success "+ localPort;

    }


    @GetMapping("/get")
    public String get(HttpServletRequest request) {
        int localPort = request.getLocalPort();
        System.out.println(">>>>>>>>>>>>>>  我的端口号是："+ localPort);
        // 锁的键值【transaction】
        String lockKey = "orderLock";
        // 设置客户端id
        String clientId = UUID.randomUUID().toString();
        try {
            // 设置锁    如果redis中已经存在 lockKey 则会添加失败
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);

            if (result == null || !result) {
                return "error";
            }
            String stock = stringRedisTemplate.opsForValue().get("stock");
            if (StrUtil.isEmpty(stock)) {
                return ">>>>>>>> 数据异常请稍后重试";
            }
            int number = Integer.parseInt(stock);
            if (number > 0) {
                Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println(">>>>>>>>>>> 扣除库存成功，剩余：" + lastNumber);
            } else {
                System.out.println(">>>>>>>>>>> 扣除库存失败，库存不足");
            }
        } finally {
            // 如果客户端id相等
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "success "+ localPort;
    }

    @GetMapping("/redisson")
    public String redisson(HttpServletRequest request) {
        int localPort = request.getLocalPort();
        System.out.println(">>>>>>>>>>>>>>  我的端口号是："+ localPort);
        String lockKey = "orderLock";
        // 获取锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 加锁
            lock.lock();

            String stock = stringRedisTemplate.opsForValue().get("stock");
            if (StrUtil.isEmpty(stock)) {
                return ">>>>>>>> 数据异常请稍后重试";
            }
            int number = Integer.parseInt(stock);
            if (number > 0) {
                Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println(">>>>>>>>>>> 扣除库存成功，剩余：" + lastNumber);
            } else {
                System.out.println(">>>>>>>>>>> 扣除库存失败，库存不足");
            }

        } finally {
            System.out.println(">>>>>>>>>>>>>>>>> 解除锁定");
            // 解锁
            lock.unlock();
        }
        return "success "+ localPort;
    }


//    @LogAction(value = "test2")
    @LockAction(value = "unlock2")
    @GetMapping("/unlock/{parameter}")
    public String unlock(@PathVariable("parameter") String parameter,HttpServletRequest request) {
        int localPort = request.getLocalPort();
        System.out.println(">>>>>>>>>>>>>>  我的端口号是："+ localPort);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> : "+ System.currentTimeMillis());
        String stock = stringRedisTemplate.opsForValue().get("stock");
        int number = Integer.parseInt(stock);
        if (number > 0) {
            Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
            System.out.println(">>>>>>>>>>> 扣除库存成功，剩余：" + lastNumber);
        } else {
            System.out.println(">>>>>>>>>>> 扣除库存失败，库存不足");
        }
        return "success" + localPort;
    }


    /**
     *  红锁/
     */
    @GetMapping("/redlock")
    public String redlock() throws InterruptedException {
        String lockKey = "lock";

        /*
            【红锁】：
            假设有5个redis节点，这些节点之间既没有主从，也没有集群关系。
            客户端用相同的key和随机值在5个节点上请求锁，请求锁的超时时间应小于锁自动释放时间。
            当在3个（超过半数）redis上请求到锁的时候，才算是真正获取到了锁。
            如果没有获取到锁，则把部分已锁的redis释放掉
         */

        // 该对象也可以用来将多个RLock,对象关联为一个红锁，【每个RLock对象实例可以来自于不同的Redisson实例】
        RLock lock1 = redissonClient.getLock(lockKey);
        RLock lock2 = redissonClient.getLock(lockKey);
        RLock lock3 = redissonClient.getLock(lockKey);

        /**
         * 这里构建多个 RLock
         */
        RedissonRedLock redissonRedLock = new RedissonRedLock(lock1, lock2, lock3);

        try {
            // 同时加锁：lock1 lock2 lock3, 红锁在[大部分节点]上加锁成功就算成功。
            boolean result = redissonRedLock.tryLock(30, 60, TimeUnit.SECONDS);

            if (result) {
                // 做业务
                System.out.println("进入业务层  <----------------------");
            }
        } finally {
            // 解除锁定
            redissonRedLock.unlock();
        }

        return "success";
    }


    @GetMapping("/add")
    public String add() {
        Book book = new Book();
        book.setAuthor("曹雪芹");
        book.setBookName("《红楼梦》");
        book.setReleaseTime(new Date());

        redisTemplate.opsForValue().set("add", book);
        return "success";
    }

    @GetMapping("/getAdd")
    public Book getAdd() {
        // 获取书名称
        Book book = (Book) redisTemplate.opsForValue().get("add");

        redisTemplate.opsForValue().set("keyOne", "testTwo", Duration.ofSeconds(30));
        redisTemplate.opsForValue().set("keyTwo", "testThree", 300L, TimeUnit.SECONDS);


        return book;
    }

    @GetMapping("/remove/{key}")
    public String remove(@PathVariable("key") String key) {

        Boolean keyTwo = redisTemplate.delete(key);
        Assert.isTrue(keyTwo, ">>>>>>>>>>>>>> 删除键值失败");

        return "success";
    }

    @GetMapping("/order")
    public String order() {
        // 获取键值，若没有键值则会新建，并初始化为 0
        Long orderNumber = redisTemplate.opsForValue().increment("order:number");
        // 设置过期时间
        redisTemplate.expire("order:number",10 ,TimeUnit.SECONDS);
        Object o = redisTemplate.opsForValue().get("order:number");
        System.out.println("o.toString() = " + o.toString());
        return o.toString();
    }


}
